Skip to content

fix(invitation): remove user relation on invitation delete#1678

Open
whoAbhishekSah wants to merge 1 commit into
mainfrom
fix/invitation-relation-cleanup
Open

fix(invitation): remove user relation on invitation delete#1678
whoAbhishekSah wants to merge 1 commit into
mainfrom
fix/invitation-relation-cleanup

Conversation

@whoAbhishekSah
Copy link
Copy Markdown
Member

What

Invitation creation writes two SpiceDB tuples anchored on the invitation object:

  • app/invitation:<id>#user@app/user:<email>
  • app/invitation:<id>#org@app/organization:<org>

Invitation.Delete filtered the relation delete by the org relation name only, so the #user tuple was left behind on every accept, expire, and delete. These orphan tuples accumulated in SpiceDB over time.

Fix

Drop the RelationName filter in Delete so it removes every relation anchored on the invitation object (the same pattern role.Delete already uses). Accept already routes its cleanup through Delete, so accept is covered too.

Test

Extends the existing TestInvitationAPI accept flow with an assertion that, after accepting an invitation, ListRelations returns no relation on the invitation object. Verified the assertion fails without the fix (leftover #user tuple) and passes with it.

Addresses gap (3) of #1661.

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Jun 8, 2026 9:18am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 5, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c7d1b1dc-c271-4afa-8a75-15638b49cdbe

📥 Commits

Reviewing files that changed from the base of the PR and between 47109aa and 51a4eb2.

📒 Files selected for processing (2)
  • core/invitation/service.go
  • test/e2e/regression/api_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/e2e/regression/api_test.go
  • core/invitation/service.go

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Fixed an issue where some invitation-related data was not being fully removed when invitations were accepted, deleted, or expired, preventing orphaned records from remaining in the system.
  • Tests

    • Added regression test to verify that all invitation-related data is properly cleaned up after acceptance.

Walkthrough

Broadened Service.Delete relation removal to target all relation tuples anchored on the invitation object (no RelationName), and added a regression test that asserts no SpiceDB relations remain for the invitation after AcceptOrganizationInvitation.

Changes

Invitation relation cleanup fix

Layer / File(s) Summary
Service.Delete relation cleanup fix
core/invitation/service.go
Service.Delete now calls relationService.Delete with only the invitation object (namespace and ID) and omits RelationName, so both #user and #org tuples anchored to the invitation are deleted. Inline comments explain createRelations writes both tuples and why omitting RelationName is required.
Invitation acceptance relation cleanup regression test
test/e2e/regression/api_test.go
After AcceptOrganizationInvitation, the test queries SpiceDB via the admin client for relations tied to the invitation object and asserts the returned relations set is empty, ensuring no invitation-object tuples remain.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • rohilsurana
  • rohanchkrabrty
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coveralls
Copy link
Copy Markdown

coveralls commented Jun 5, 2026

Coverage Report for CI Build 27123722556

Coverage increased (+0.004%) to 43.414%

Details

  • Coverage increased (+0.004%) from the base build.
  • Patch coverage: 4 of 4 lines across 1 file are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 38073
Covered Lines: 16529
Line Coverage: 43.41%
Coverage Strength: 12.21 hits per line

💛 - Coveralls

Invitation creation writes two SpiceDB tuples on the invitation object:
app/invitation:<id>#user and app/invitation:<id>#org. Delete filtered by
the org relation name only, so the #user tuple leaked on every accept,
expire, and delete, accumulating orphan relations.

Drop the relation-name filter so Delete removes every relation anchored on
the invitation object (same pattern role.Delete already uses).

Adds an e2e regression assertion: after accepting an invitation, no
relation remains on the invitation object.

Refs #1661

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@whoAbhishekSah
Copy link
Copy Markdown
Member Author

whoAbhishekSah commented Jun 8, 2026

Manual testing notes

Tested this against a local Frontier build of this branch (Postgres + SpiceDB). Summary below.

What an invitation creates

For each CreateOrganizationInvitation we confirmed it writes:

  • 1 row in the invitations table (Frontier DB)
  • 2 SpiceDB tuples on the invitation object:
    • app/invitation:<id>#user@app/user:<email-slug>
    • app/invitation:<id>#org@app/organization:<org-id>

What we tested

Created a fresh org and two invitations (one for each cleanup path), then checked the DB and SpiceDB before and after:

  1. Delete path — called DeleteOrganizationInvitation. After delete: the DB row was gone and both the #user and #org tuples were removed.
  2. Accept path — the invited user called AcceptOrganizationInvitation. After accept: the DB row was gone, both tuples were removed, and the user was correctly added as an org member.

In both cases SpiceDB had marked the tuples as deleted (it does a soft delete, setting a deleted_xid on the row rather than physically removing it), so they no longer show up as live relations.

Bonus: the bug was real

Before running anything, the test DB already had 2 leftover #user tuples with no matching DB row and no #org tuple — exactly the leak the old Delete produced. After the fix, no new orphans are created.

Note

This PR stops new leaks but does not clean up tuples that already leaked before the fix. Cleaning up existing orphans is a separate task.

✅ Working as expected.

@whoAbhishekSah whoAbhishekSah marked this pull request as ready for review June 8, 2026 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants